package com.helin.helinhealth.service.impl;


import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.helin.helinhealth.entity.Medicine;
import com.helin.helinhealth.mapper.MedicineMapper;
import com.helin.helinhealth.service.MedicineService;
import com.helin.helinhealth.utils.UserContext;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.function.Function;


/**
 * 药品服务实现类
 * 
 * <p>
 * 实现{@link MedicineService}接口定义的所有方法，提供药品管理相关的具体业务逻辑。
 * 继承自MyBatis-Plus的ServiceImpl，自动拥有基础的CRUD能力。
 * </p>
 * 
 * <p>
 * 该服务实现了以下主要功能：
 * - 药品信息的添加和更新，包括权限验证
 * - 获取用户药品列表、今日待服用药品
 * - 药品库存管理和预警
 * - 服药打卡功能
 * </p>
 * 
 * <p>
 * 所有可能影响数据一致性的方法都添加了{@code @Transactional}注解，
 * 确保操作的原子性，避免数据不一致问题。
 * </p>
 */
@Slf4j
@Service
public class MedicineServiceImpl extends ServiceImpl<MedicineMapper, Medicine> implements MedicineService {

    /**
     * 药品数据访问对象
     */
    @Resource
    private MedicineMapper medicineMapper;

    /**
     * 添加或更新药品信息
     * 
     * <p>
     * 根据药品ID是否存在，自动判断是新增还是更新操作。
     * 新增时自动关联当前登录用户，更新时进行权限验证，
     * 确保用户只能修改自己的药品信息。
     * </p>
     * 
     * @param medicine 药品信息对象
     * @throws RuntimeException 当权限验证失败或操作异常时抛出
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveOrupdate(Medicine medicine) {
        // 判断是新增还是更新操作
        boolean isUpdate = medicine.getId() != null;
        
        // 如果是新增药品，设置当前用户ID
        if (!isUpdate) {
            String currentUserOpenid = UserContext.getCurrentUserOpenid();
            medicine.setUserId(currentUserOpenid);
            log.info("新增药品信息: {}", medicine.getName());
        } else {
            log.info("更新药品信息, ID: {}, 名称: {}", medicine.getId(), medicine.getName());
            
            // 进行更新前的检查，验证当前用户是否有权限更新此药品
            Medicine existingMedicine = this.getById(medicine.getId());
            if (existingMedicine == null) {
                log.warn("要更新的药品不存在, ID: {}", medicine.getId());
                throw new RuntimeException("药品不存在");
            }
            
            // 防止更新时修改用户ID，确保数据安全
            medicine.setUserId(existingMedicine.getUserId());
        }
        
        // 执行保存或更新操作
        boolean success = super.saveOrUpdate(medicine);
        
        // 根据操作结果记录日志，失败时抛出异常
        if (success) {
            log.info("药品{}成功, ID: {}", isUpdate ? "更新" : "新增", medicine.getId());
        } else {
            log.error("药品{}失败", isUpdate ? "更新" : "新增");
            throw new RuntimeException("操作失败");
        }
    }

    /**
     * 获取指定用户的药品列表
     * 
     * <p>
     * 查询指定用户的所有有效药品，按创建时间降序排序。
     * </p>
     * 
     * @param userId 用户ID (openid)
     * @return 药品列表
     */
    @Override
    public List<Medicine> getUserMedicineList(String userId) {
        return medicineMapper.getUserMedicineList(userId);
    }

    /**
     * 获取当前登录用户今日需服用的药品
     * 
     * <p>
     * 查询当前用户今日尚未服用的药品，按服药时间升序排序。
     * 用于实现首页的服药提醒功能。
     * </p>
     * 
     * @return 今日待服用的药品列表
     */
    @Override
    public List<Medicine> getCurrentUserTodayMedicines() {
        // 获取当前登录用户的openid
        String currentUserOpenid = UserContext.getCurrentUserOpenid();
        // 调用Mapper查询今日待服用的药品
        return medicineMapper.getTodayMedicines(currentUserOpenid);
    }

    /**
     * 获取库存低于指定阈值的药品
     * 
     * <p>
     * 查询当前用户库存天数不足的药品，用于药品库存预警功能。
     * 按库存天数升序排序，库存越少的药品排在越前面。
     * </p>
     * 
     * @param threshold 库存阈值（天数）
     * @return 库存低的药品列表
     */
    @Override
    public List<Medicine> getLowStockMedicines(Integer threshold) {
        // 获取当前登录用户的openid
        String currentUserOpenid = UserContext.getCurrentUserOpenid();
        // 调用Mapper查询库存低的药品
        return medicineMapper.getLowStockMedicines(currentUserOpenid, threshold);
    }

    /**
     * 更新药品服用状态
     * 
     * <p>
     * 更新指定药品的服用状态，用于实现服药打卡功能。
     * 使用乐观锁机制防止并发更新问题，会进行权限验证，
     * 确保用户只能更新自己的药品状态。
     * </p>
     * 
     * @param id 药品ID
     * @param isTaken 是否已服用
     * @return 更新是否成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateMedicineTakenStatus(Long id, Boolean isTaken) {
        // 先查询药品信息，确认存在并获取版本号
        Medicine medicine = this.getById(id);
        if (medicine == null) {
            log.warn("要更新的药品不存在, ID: {}", id);
            return false;
        }
        
        // 验证当前用户是否有权限更新该药品
        String currentUserOpenid = UserContext.getCurrentUserOpenid();
        if (!currentUserOpenid.equals(medicine.getUserId())) {
            log.warn("当前用户无权更新此药品, 用户ID: {}, 药品ID: {}", currentUserOpenid, id);
            return false;
        }
        
        // 使用乐观锁更新药品服用状态
        int result = medicineMapper.updateMedicineTakenStatus(id, isTaken, medicine.getVersion());
        
        // 根据更新结果记录日志并返回
        if (result > 0) {
            log.info("药品服用状态更新成功, ID: {}, 状态: {}", id, isTaken);
            return true;
        } else {
            log.warn("药品服用状态更新失败, ID: {}, 可能是版本号冲突", id);
            return false;
        }
    }

    /**
     * 减少药品库存天数
     * 
     * <p>
     * 减少指定药品的库存天数，用于记录用户服药后的库存变化。
     * 会确保库存天数不为负数，并在库存不足时进行预警。
     * 使用乐观锁机制防止并发更新问题。
     * </p>
     * 
     * @param id 药品ID
     * @param days 减少的天数
     * @return 更新是否成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean reduceStockDays(Long id, Integer days) {
        // 先查询药品信息，确认存在并获取版本号
        Medicine medicine = this.getById(id);
        if (medicine == null) {
            log.warn("要更新的药品不存在, ID: {}", id);
            return false;
        }
        
        // 验证当前用户是否有权限更新该药品
        String currentUserOpenid = UserContext.getCurrentUserOpenid();
        if (!currentUserOpenid.equals(medicine.getUserId())) {
            log.warn("当前用户无权更新此药品, 用户ID: {}, 药品ID: {}", currentUserOpenid, id);
            return false;
        }
        
        // 计算新的库存天数，不能小于0
        int newStockDays = Math.max(0, medicine.getStockDays() - days);
        
        // 使用乐观锁更新库存天数
        LambdaUpdateWrapper<Medicine> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(Medicine::getId, id)
                    .eq(Medicine::getVersion, medicine.getVersion())
                    .set(Medicine::getStockDays, newStockDays)
                    .set(Medicine::getVersion, medicine.getVersion() + 1);
        
        // 执行更新操作
        boolean success = this.update(updateWrapper);
        
        // 根据更新结果记录日志并返回
        if (success) {
            log.info("药品库存天数更新成功, ID: {}, 新库存天数: {}", id, newStockDays);
            
            // 如果库存天数不足，记录警告日志
            if (newStockDays <= 3) {
                log.warn("药品库存不足, ID: {}, 名称: {}, 剩余天数: {}", id, medicine.getName(), newStockDays);
            }
            
            return true;
        } else {
            log.warn("药品库存天数更新失败, ID: {}, 可能是版本号冲突", id);
            return false;
        }
    }
}